﻿using CTP.CRM.Core.ExRedis.Options;
using Newtonsoft.Json;
using StackExchange.Redis;
using System.ComponentModel;
using System.Globalization;

namespace CTP.CRM.Core.ExRedis
{
    public class ExRedisClient : IDisposable
    {
        private readonly ConfigurationOptions _redisOptions;

        private readonly ConnectionMultiplexer _connection;

        private readonly IDatabase _db;

        private const string Tag = "CTP";

        public IDatabase Db => _db;

        public ExRedisClient(ExRedisSetting setting)
        {
            _redisOptions = new ConfigurationOptions
            {
                User = setting.User,
                Password = setting.Password,
                Ssl = setting.Ssl,
                DefaultDatabase = setting.Database,
                ConnectTimeout = setting.ConnectTimeout * 1000,
                SyncTimeout = setting.SyncTimeout * 1000,
                AsyncTimeout = setting.SyncTimeout * 1000,
                ConnectRetry = setting.ConnectRetry,
                EndPoints = { setting.Host }
            };
            _connection = ConnectionMultiplexer.Connect(_redisOptions);
            _db = _connection.GetDatabase(setting.Database);
        }

        public async Task<long> AppendAsync<T>(string key, T value)
        {
            return await _db.StringAppendAsync(getCacheKey(key), SerializeRedisValue(value));
        }

        public long Append<T>(string key, T value)
        {
            return _db.StringAppend(getCacheKey(key), SerializeRedisValue(value));
        }

        public bool Exist(string key)
        {
            return _db.KeyExists(getCacheKey(key));
        }

        public bool Lock(string key, int timeoutSeconds)
        {
            return _db.LockTake("RedisLock_" + key, "CTP", TimeSpan.FromSeconds(timeoutSeconds));
        }

        public LockControl AutoLock(string key, int timeoutSeconds, bool isWait = false)
        {
            return new LockControl(this, key, timeoutSeconds, isWait);
        }

        public bool Unlock(string key)
        {
            return _db.LockRelease("RedisLock_" + key, "CTP");
        }

        public bool WaitLock(string key, int timeoutSeconds)
        {
            string text = "RedisLock_" + key;
            DateTime dateTime = DateTime.Now.AddSeconds(timeoutSeconds);
            while (DateTime.Now < dateTime)
            {
                bool flag = _db.StringSet(text, "CTP", TimeSpan.FromSeconds(timeoutSeconds), When.NotExists);
                if (flag)
                {
                    return flag;
                }

                Thread.CurrentThread.Join(2000);
            }

            return false;
        }

        public async Task<long> DelAsync(params string[] keys)
        {
            RedisKey[] param = keys.Select((string x) => new RedisKey(x)).ToArray();
            return await _db.KeyDeleteAsync(param);
        }

        public async Task<long> Delete(string[] keys)
        {
            return await DelAsync(keys);
        }

        public async Task<bool> HSetAsync<T>(string key, string field, T value)
        {
            string cacheValue = SerializeRedisValue(value);
            return await _db.HashSetAsync(getCacheKey(key), field, cacheValue);
        }

        public bool HSet<T>(string key, string field, T value)
        {
            return _db.HashSet(getCacheKey(key), field, SerializeRedisValue(value));
        }

        public void HMSet<T>(string key, Dictionary<string, T> keyValues)
        {
            HashEntry[] hashFields = keyValues.Select<KeyValuePair<string, T>, HashEntry>((KeyValuePair<string, T> x) => new HashEntry(x.Key, SerializeRedisValue(x.Value))).ToArray();
            _db.HashSet(getCacheKey(key), hashFields);
        }

        public bool SetExpire(string key, uint expirySeconds)
        {
            return _db.KeyExpire(getCacheKey(key), TimeSpan.FromSeconds(expirySeconds));
        }

        public bool SetExpire(string key, TimeSpan timeSpan)
        {
            return _db.KeyExpire(getCacheKey(key), timeSpan);
        }

        public bool SetExpire(string key, DateTime datetime)
        {
            return _db.KeyExpire(getCacheKey(key), datetime);
        }

        public bool Delete(string key)
        {
            return _db.KeyDelete(getCacheKey(key));
        }

        public T Get<T>(string key)
        {
            RedisValue redisValue = _db.StringGet(getCacheKey(key));
            return redisValue.HasValue ? DeserializeRedisValue<T>(redisValue) : default(T);
        }

        public async Task<string?> GetAsync(string key)
        {
            return await _db.StringGetAsync(getCacheKey(key));
        }

        public async Task<T?> GetAsync<T>(string key)
        {
            return DeserializeRedisValue<T>(await _db.StringGetAsync(getCacheKey(key)));
        }

        public async Task<bool> ExistsAsync(string key)
        {
            return await _db.KeyExistsAsync(getCacheKey(key));
        }

        public long Incr(string key)
        {
            return _db.StringIncrement(getCacheKey(key), 1L);
        }

        public long Incr(string key, long value)
        {
            return _db.StringIncrement(getCacheKey(key), value);
        }

        public long IncrBy(string key, long increment)
        {
            return _db.StringIncrement(getCacheKey(key), increment);
        }

        public async Task<long> IncrAsync(string key)
        {
            return await _db.StringIncrementAsync(getCacheKey(key), 1L);
        }

        public async Task<long> IncrByAsync(string key, long increment)
        {
            return await _db.StringIncrementAsync(getCacheKey(key), increment);
        }

        public async Task<long> ExistsAsync(string[] keys)
        {
            RedisKey[] param = keys.Select((string x) => new RedisKey(x)).ToArray();
            return await _db.KeyExistsAsync(param);
        }

        public async Task<bool> ExpireAsync(string key, int seconds)
        {
            return await _db.KeyExpireAsync(getCacheKey(key), DateTime.Now.AddSeconds(seconds));
        }

        public bool Expire(string key, int seconds)
        {
            return _db.KeyExpire(getCacheKey(key), DateTime.Now.AddSeconds(seconds));
        }

        public bool Expire(string key, TimeSpan timeSpan)
        {
            return _db.KeyExpire(getCacheKey(key), timeSpan);
        }

        public bool HSetNx<T>(string key, string field, T value)
        {
            return _db.HashSet(getCacheKey(key), field, SerializeRedisValue(value), When.NotExists);
        }

        public async Task<long> HIncrByAsync(string key, string field, long increment)
        {
            return await _db.HashIncrementAsync(getCacheKey(key), field, increment);
        }

        public async Task<long> HDelAsync(string key, params string[] fields)
        {
            RedisValue[] keys = fields.Select((string x) => new RedisValue(x)).ToArray();
            return await _db.HashDeleteAsync(getCacheKey(key), keys);
        }

        public bool Set<T>(string key, T value, int timeoutSeconds = 600)
        {
            return _db.StringSet(getCacheKey(key), SerializeRedisValue(value), TimeSpan.FromSeconds(timeoutSeconds));
        }

        public bool Set<T>(string key, T value, TimeSpan timeout)
        {
            return _db.StringSet(getCacheKey(key), SerializeRedisValue(value), timeout);
        }

        public async Task<bool> SetAsync<T>(string key, T value, int timeoutSeconds = 600)
        {
            return await _db.StringSetAsync(getCacheKey(key), SerializeRedisValue(value), TimeSpan.FromSeconds(timeoutSeconds));
        }

        public async Task SetAsync<T>(string key, T value, TimeSpan timeout)
        {
            await _db.StringSetAsync(getCacheKey(key), SerializeRedisValue(value), timeout);
        }

        public async Task<bool> HExistsAsync(string key, string field)
        {
            return await _db.HashExistsAsync(getCacheKey(key), field);
        }

        public async Task<string?> HGetAsync(string key, string field)
        {
            return await _db.HashGetAsync(getCacheKey(key), field);
        }

        public async Task<T?> HGetAsync<T>(string key, string field)
        {
            return DeserializeRedisValue<T>(await _db.HashGetAsync(getCacheKey(key), field));
        }

        public async Task HSetAsync<T>(string key, Dictionary<string, T> keyValues)
        {
            HashEntry[] data = keyValues.Select<KeyValuePair<string, T>, HashEntry>((KeyValuePair<string, T> x) => new HashEntry(x.Key, SerializeRedisValue(x.Value))).ToArray();
            await _db.HashSetAsync(getCacheKey(key), data);
        }

        public long HDel(string key, params string[] fields)
        {
            RedisValue[] hashFields = fields.Select((string x) => new RedisValue(x)).ToArray();
            return _db.HashDelete(getCacheKey(key), hashFields);
        }

        public bool HExists(string key, string field)
        {
            return _db.HashExists(getCacheKey(key), field);
        }

        public string? HGet(string key, string field)
        {
            return _db.HashGet(getCacheKey(key), field);
        }

        public async Task<Dictionary<string, string>> HGetAllAsync(string key)
        {
            HashEntry[] data = await _db.HashGetAllAsync(getCacheKey(key));
            if (data != null && data.Length != 0)
            {
                return data.ToDictionary((HashEntry x) => x.Name.ToString(), (HashEntry v) => v.Value.ToString());
            }

            return new Dictionary<string, string>();
        }

        public async Task<Dictionary<string, T>> HGetAllAsync<T>(string key)
        {
            HashEntry[] data = await _db.HashGetAllAsync(getCacheKey(key));
            if (data != null && data.Length != 0)
            {
                return data.ToDictionary((HashEntry x) => x.Name.ToString(), (HashEntry v) => DeserializeRedisValue<T>(v.Value));
            }

            return new Dictionary<string, T>();
        }

        public async Task<bool> HSetAsync<T>(string key, string field, string value)
        {
            return await _db.HashSetAsync(getCacheKey(key), field, value);
        }

        public async Task<bool> SetStringAsync(string key, string value, uint expirySeconds)
        {
            return await _db.StringSetAsync(getCacheKey(key), value, TimeSpan.FromSeconds(expirySeconds));
        }

        public async Task<string?> GetStringAsync(string key)
        {
            return await _db.StringGetAsync(getCacheKey(key));
        }

        public async Task<bool> KeyExistsAsync(string key)
        {
            return await _db.KeyExistsAsync(getCacheKey(key));
        }

        public async Task RemoveKeyAsync(string key)
        {
            await _db.KeyDeleteAsync(getCacheKey(key));
        }

        public async Task<long> TtlAsync(string key)
        {
            TimeSpan? span = await _db.KeyTimeToLiveAsync(getCacheKey(key));
            if (!span.HasValue)
            {
                return 0L;
            }

            return Convert.ToInt64(span?.TotalSeconds);
        }

        public async Task<TimeSpan> TtlTimeSpanAsync(string key)
        {
            return (await _db.KeyTimeToLiveAsync(getCacheKey(key))).GetValueOrDefault();
        }

        private string getCacheKey(string key)
        {
            return string.IsNullOrEmpty(key) ? "null" : key;
        }

        private string SerializeRedisValue<T>(T value)
        {
            if (value == null)
            {
                return string.Empty;
            }

            if ((object)value is string result)
            {
                return result;
            }

            if (typeof(T).IsValueType)
            {
                return (string)Convert.ChangeType(value, typeof(string));
            }

            return JsonConvert.SerializeObject(value);
        }

        private T DeserializeRedisValue<T>(string value)
        {
            if (string.IsNullOrEmpty(value))
            {
                return default(T);
            }

            Type typeFromHandle = typeof(T);
            if (typeFromHandle == typeof(string))
            {
                return (T)(object)value;
            }

            string text = typeFromHandle.ToString().TrimEnd(']');
            if (text == "System.String")
            {
                return (T)(object)value;
            }

            if (text == "System.Byte[" || text == "System.Boolean[")
            {
                return (T)Convert.ChangeType(value, typeFromHandle);
            }

            if (typeFromHandle.IsValueType)
            {
                string text2 = (text.StartsWith("System.Nullable`1[") ? text.Substring(18) : text);
                bool flag = false;
                object obj = null;
                long result2;
                switch (text2)
                {
                    case "System.Boolean":
                        if (value == "1" || string.Equals("true", value, StringComparison.OrdinalIgnoreCase))
                        {
                            obj = true;
                        }
                        else if (value == "0" || string.Equals("false", value, StringComparison.OrdinalIgnoreCase))
                        {
                            obj = false;
                        }

                        break;
                    case "System.Byte":
                        {
                            if (byte.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat, out var result8))
                            {
                                obj = result8;
                            }

                            break;
                        }
                    case "System.Char":
                        if (value.Length > 0)
                        {
                            obj = value[0];
                        }

                        break;
                    case "System.Decimal":
                        {
                            if (decimal.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat, out var result4))
                            {
                                obj = result4;
                            }

                            break;
                        }
                    case "System.Double":
                        {
                            if (double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat, out var result13))
                            {
                                obj = result13;
                            }

                            break;
                        }
                    case "System.Single":
                        {
                            if (float.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat, out var result10))
                            {
                                obj = result10;
                            }

                            break;
                        }
                    case "System.Int32":
                        {
                            if (int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat, out var result6))
                            {
                                obj = result6;
                            }

                            break;
                        }
                    case "System.Int64":
                        if (long.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat, out result2))
                        {
                            obj = result2;
                        }

                        break;
                    case "System.SByte":
                        {
                            if (sbyte.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat, out var result14))
                            {
                                obj = result14;
                            }

                            break;
                        }
                    case "System.Int16":
                        {
                            if (short.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat, out var result12))
                            {
                                obj = result12;
                            }

                            break;
                        }
                    case "System.UInt32":
                        {
                            if (uint.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat, out var result11))
                            {
                                obj = result11;
                            }

                            break;
                        }
                    case "System.UInt64":
                        {
                            if (ulong.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat, out var result9))
                            {
                                obj = result9;
                            }

                            break;
                        }
                    case "System.UInt16":
                        {
                            if (ushort.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat, out var result7))
                            {
                                obj = result7;
                            }

                            break;
                        }
                    case "System.DateTime":
                        {
                            if (DateTime.TryParse(value, out var result5))
                            {
                                obj = result5;
                            }

                            break;
                        }
                    case "System.DateTimeOffset":
                        {
                            if (DateTimeOffset.TryParse(value, out var result3))
                            {
                                obj = result3;
                            }

                            break;
                        }
                    case "System.TimeSpan":
                        if (long.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture.NumberFormat, out result2))
                        {
                            obj = new TimeSpan(result2);
                        }

                        break;
                    case "System.Guid":
                        {
                            if (Guid.TryParse(value, out var result))
                            {
                                obj = result;
                            }

                            break;
                        }
                    default:
                        flag = true;
                        break;
                }

                if (!flag)
                {
                    if (obj == null)
                    {
                        return default(T);
                    }

                    return (T)obj;
                }
            }

            if (typeFromHandle.IsClass)
            {
                T val = JsonConvert.DeserializeObject<T>(value);
                return (val != null) ? val : default(T);
            }

            return (T)Convert.ChangeType(value, typeFromHandle);
        }

        private string ConvertToString<T>(T value)
        {
            if (value == null)
            {
                return string.Empty;
            }

            TypeConverter converter = TypeDescriptor.GetConverter(typeof(T));
            return converter.ConvertToString(value);
        }

        public void Dispose()
        {
            _connection.Dispose();
        }
    }
}
